/*   **********************************************************************  **
 **   Copyright notice                                                       **
 **                                                                          **
 **   (c) 2009, Around(J2)ME				                                 **
 **   All rights reserved.                                                   **
 **                                                                          **
 **   This program and the accompanying materials are made available under   **
 **   the terms of the New BSD License which accompanies this    			 **
 **   distribution.											                 **
 **   A copy is found in the textfile LICENSE.txt							 **
 **                                                                          **
 **   This copyright notice MUST APPEAR in all copies of the file!           **
 **                                                                          **
 **   Main developers:                                                       **
 **     Juri Strumpflohner		http://blog.js-development.com	             **
 **		Matthias Braunhofer		http://matthias.jimdo.com                    **
 **                                                                          **
 **  **********************************************************************  */

package org.aroundme.controller;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.Hits;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.LockObtainFailedException;
import org.aroundme.model.Comment;
import org.aroundme.model.DateManager;
import org.aroundme.model.Location;
import org.aroundme.model.Place;
import org.aroundme.model.User;
import org.aroundme.model.XMLRequestParser;
import org.aroundme.persistency.LocationManager;
import org.aroundme.persistency.PlaceManager;
import org.aroundme.persistency.UserManager;
import org.aroundme.services.GeoSearcher;
import org.aroundme.services.Indexer;
import org.aroundme.services.PlaceDocument;
import org.aroundme.utility.Constants;
import org.aroundme.utility.Pair;
import org.jdom.JDOMException;
/**
 * This class is responsible for controlling the whole application. It does
 * the request handling job and calls the appropriate managers of the persistence
 * layer for accessing the database.
 * 
 */
public class ApplicationController {
	private static Log log = LogFactory.getLog(ApplicationController.class);
	//Controllers
	private ResponseController responseController = null;
	
	//other
	private PlaceManager placeMan = null;
	private LocationManager locMan = null;
	private UserManager userMan = null;
	private XMLRequestParser requestParser = null;
	private Indexer indexer = null;
	private Directory directory = null;
	private String indexDirPath = "AroundJ2ME-Index";
	
	public ApplicationController() {
	}
	
	/**
	 * Handles the incoming request by analyzing the type of request
	 * and calling the right methods for processing it
	 *
	 */
	public void handleRequest(){
		try {
			//forward to right controller
			if(requestParser.getType().equals(Constants.COMMIT_PLACE)){
				log.debug("Handling store request");
				storeRequest();
			}else if(requestParser.getType().equals(Constants.COMMIT_PLACE_COMMENT)){
				log.debug("Handling comment request");
				storePlaceComment();
			}else if(requestParser.getType().equals(Constants.RETRIEVE_PLACE)){
				log.debug("Handling retrieve request");
				retrieveRequest();
			}else if(requestParser.getType().equals(Constants.DELETE_PLACE)){
				log.debug("Handling delete request");
				deleteRequest();
			}
		} catch (JDOMException e) {
			log.error(e.getStackTrace());
		} catch (Exception e) {
			log.error(e.getStackTrace());
		}
	}
	
	/**
	 * This method is responsible for handling store-requests of a place. This
	 * may represent also an update of an edited place which is submitted by the client.
	 * @throws Exception
	 */
	public void storeRequest() throws Exception{
		String errorMsg = null;
		try {
			ArrayList<Place> places = requestParser.getPlaces();
			
			//process location of each place
			for (Place place : places) {
				Location placeLoc = locMan.getLocationWithCellId(place.getLocation().getCellid());
				if(placeLoc == null){
					//save new location
					placeLoc = place.getLocation();
				}
				
				//check if place already exists in the DB (in case of an edit-store request)
				Place dbPlace = placeMan.retrievePlaceWithId(place.getId());
				if(dbPlace != null){
					//TODO do this merging via reflection automatically in some helper class
					//is an edit operation
					dbPlace.setName(place.getName());
					dbPlace.setAddress(place.getAddress());
					dbPlace.setCity(place.getCity());
					dbPlace.setPhone(place.getPhone());
					
					placeMan.savePlace(dbPlace);
					indexer.index(dbPlace, true);
				}else{
					//add the creation date to the place
					place.setCreationDate(new DateManager().getCurrentDate());
					placeLoc.addPlace(place);
				
					User placeUser = userMan.getUserByLoginName(place.getUser().getLoginName());
					if(placeUser != null){
						place.setUser(placeUser);
					}else{
						throw new Exception("User not registered!");
					}
					
					locMan.saveLocation(placeLoc);
					indexer.index(place, false);
				}
			}
		} catch (Exception e) {
			errorMsg = e.getMessage();
			throw e;
		} finally{
			if(errorMsg!=null)
				responseController.sendAlert(Constants.ALERT_ERROR, errorMsg);
			else
				responseController.sendAlert(Constants.ALERT_INFO, "Place successfully saved!");
		}
	}
	
	/**
	 * This method is responsible for handling comment-store requests.
	 * @throws Exception
	 */
	public void storePlaceComment() throws Exception {
		String errorMsg = null;
		try{
			Comment c = requestParser.getComment();
			
			//retrieve User and Place objects from DB
			User u = userMan.getUserByLoginName(c.getUser().getLoginName());
			Place p = placeMan.retrievePlaceWithId(c.getPlace().getId());
			if(p!=null && u!=null){
				c.setUser(u);
				c.setCreationDate(new DateManager().getCurrentDate());
				p.addComment(c);
				placeMan.savePlace(p);
			}else{
				errorMsg = "Error when saving comment! Place didn't exist!\nIt may have been deleted in the meantime!";
			}
		}catch(Exception e){
			errorMsg = "Comment wasn't stored!\nReason: " + e.getMessage();
			throw e;
		}finally{
			if(errorMsg!=null)
				responseController.sendAlert(Constants.ALERT_ERROR, errorMsg);
			else
				responseController.sendAlert(Constants.ALERT_INFO, "Comment successfully submitted!");
		}
	}
	
	/**
	 * This method handles all kind of retrieve requests, being it a retrieve based on location-
	 * data or a retrieve of personal places
	 * @throws Exception
	 */
	public void retrieveRequest() throws Exception{
		boolean sendReturn = false;
		List<Place> placesToReturn = null;

		try {
			if(requestParser.getRequestParameter().equals(Constants.REQPARAM_RETRIEVE_BYCATEGORY)){
				String requestedCategory = requestParser.getRequestedCategory();
				Location requestedLocation = requestParser.getRequestedLocation();
				//TODO call the appropriate method to fetch all places that match the category and location
				
				GeoSearcher searcher = new GeoSearcher(directory);
				Pair<Hits, Map<Integer, Double>> result = searcher.searchByCategory(requestedCategory, requestedLocation);
				
				placesToReturn = getPlaces(result.first, result.second, requestParser.getRequestDataBegin(), requestParser.getDataAmount());
				
				searcher.close();
				sendReturn = true;
				
			//TODO old requests below...remove them if no more necessary
			}else if(requestParser.getRequestParameter().equals(Constants.PARAM_RETRIEVE_ALL)){
				//return all places on server
				placesToReturn = placeMan.retrieveAllPlaces(requestParser.getRequestDataBegin(), requestParser.getDataAmount());
				sendReturn = true;
			} else if(requestParser.getRequestParameter().equals(Constants.PARAM_RETRIEVE_PERSONAL)){
				String userLoginName = requestParser.getUserLoginName();
				
				int begin = requestParser.getRequestDataBegin();
				int amount = requestParser.getDataAmount();
				placesToReturn = placeMan.retrieveUserPlaces(userLoginName, begin, amount);
				
				sendReturn = true;
			}else if(requestParser.getRequestParameter().equals(Constants.PARAM_SEARCH)){
				String searchQuery = requestParser.getSearchQuery();
				Location requestedLocation = requestParser.getRequestedLocation();
				
				int begin = requestParser.getRequestDataBegin();
				int amount = requestParser.getDataAmount();
				
				GeoSearcher searcher = new GeoSearcher(directory);
				Pair<Hits, Map<Integer, Double>> result = searcher.searchByTerm(searchQuery, requestedLocation);
				
				placesToReturn = getPlaces(result.first, result.second, requestParser.getRequestDataBegin(), requestParser.getDataAmount());
				
				searcher.close();
				sendReturn = true;
			}
		} catch (IOException e) {
			throw e;
		} catch (Exception e) {
			throw e;
		} finally{
			if(sendReturn){
				//check if list is not empty
				if(placesToReturn==null || placesToReturn.size()==0){
					responseController.sendAlert(Constants.ALERT_INFO, "No places have been found!");
				}else{
					responseController.sendPlaces(placesToReturn);
				}
			}
		}
	}
	
	/**
	 * This method handles all delete requests (of Places)
	 * @throws Exception
	 */
	public void deleteRequest() throws Exception {
		String errorMsg = null;
		try {
			//TODO check why requestParser returns 2 places
			Place tmp = requestParser.getPlaces().get(0);
			String userLoginName = tmp.getUser().getLoginName();
			
			//find place in DB
			Place place = placeMan.retrievePlaceWithId(tmp.getId());
			
			//start the deletion process
			if(userLoginName.equals(place.getUser().getLoginName()) && place.getId() == tmp.getId()){
				placeMan.deletePlace(place);
			}
		} catch (Exception e) {
			errorMsg = e.getMessage();
			throw e;
		}finally{
			if(errorMsg!=null)
				responseController.sendAlert(Constants.ALERT_ERROR, "Deletion failed!\nReason: " + errorMsg);
			else
				responseController.sendAlert(Constants.ALERT_INFO, "Successfully deleted!");
		}
	}
	
	/**
	 * initialize the search index
	 * @throws CorruptIndexException
	 * @throws LockObtainFailedException
	 * @throws IOException
	 */
	public void initializeIndex() throws CorruptIndexException, LockObtainFailedException, IOException {
		directory = FSDirectory.getDirectory(indexDirPath);
		indexer.setDirectory(directory);
		if (!IndexReader.indexExists(directory)) {
			indexer.init(true);
			List<Place> places = placeMan.retrieveAllPlaces(0, Integer.MAX_VALUE);
			for (Place place : places) {
				indexer.index(place, false);
			}
		} else {
			indexer.init(false);
		}
	}
	
	/**
	 * returns a list of places from the results of a Lucene search
	 * @param hits
	 * @param distances
	 * @return
	 * @throws CorruptIndexException
	 * @throws IOException
	 * @throws java.text.ParseException
	 */
	private List<Place> getPlaces(Hits hits, Map<Integer,Double> distances, int begin, int amount) throws CorruptIndexException, IOException, java.text.ParseException {
		List<Place> places = new ArrayList<Place>();
		
		int end = hits.length();
		if (begin + amount < end) {
			end = begin + amount; 
		}
		
		for(int i = begin; i < end; i++) {
			Document doc = hits.doc(i);
			
			Place place = placeMan.retrievePlaceWithId(Integer.valueOf(doc.get(PlaceDocument.ID)));
			place.setDistance(distances.get(i).doubleValue());
			
			places.add(place);
		}
		
		return places;	
	}
		
	/*
	 * Getters and setters
	 */
	public PlaceManager getPlaceMan() {
		return placeMan;
	}

	public void setPlaceMan(PlaceManager placeMan) {
		this.placeMan = placeMan;
	}

	public XMLRequestParser getRequestParser() {
		return requestParser;
	}

	public void setRequestParser(XMLRequestParser handler) {
		this.requestParser = handler;
	}

	public void setResponse(HttpServletResponse response) {
		this.responseController.setResponse(response);
	}


	public LocationManager getLocMan() {
		return locMan;
	}

	public void setLocMan(LocationManager locMan) {
		this.locMan = locMan;
	}

	public UserManager getUserMan() {
		return userMan;
	}

	public void setUserMan(UserManager userMan) {
		this.userMan = userMan;
	}

	public ResponseController getResponseController() {
		return responseController;
	}

	public void setResponseController(ResponseController responseController) {
		this.responseController = responseController;
	}

	public Indexer getIndexer() {
		return indexer;
	}

	public void setIndexer(Indexer indexer) {
		this.indexer = indexer;
	}
}
